/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2011 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_sky.c
#include "tr_local.h"
#include "gl_shader.h"

#define SKY_SUBDIVISIONS		8
#define HALF_SKY_SUBDIVISIONS	(SKY_SUBDIVISIONS/2)

static float    s_cloudTexCoords[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2];
static float    s_cloudTexP[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1];

/*
===================================================================================

POLYGON TO BOX SIDE PROJECTION

===================================================================================
*/

static vec3_t   sky_clip[6] = {
	{1, 1, 0},
	{1, -1, 0},
	{0, -1, 1},
	{0, 1, 1},
	{1, 0, 1},
	{-1, 0, 1}
};

static float    sky_mins[2][6], sky_maxs[2][6];
static float    sky_min, sky_max;

/*
================
AddSkyPolygon
================
*/
static void AddSkyPolygon(int nump, vec3_t vecs)
{
	int             i, j;
	vec3_t          v, av;
	float           s, t, dv;
	int             axis;
	float          *vp;

	// s = [0]/[2], t = [1]/[2]
	static int      vec_to_st[6][3] = {
		{-2, 3, 1},
		{2, 3, -1},

		{1, 3, 2},
		{-1, 3, -2},

		{-2, -1, 3},
		{-2, 1, -3}

		//  {-1,2,3},
		//  {1,2,-3}
	};

	// decide which face it maps to
	VectorCopy(vec3_origin, v);
	for(i = 0, vp = vecs; i < nump; i++, vp += 3)
	{
		VectorAdd(vp, v, v);
	}
	av[0] = fabs(v[0]);
	av[1] = fabs(v[1]);
	av[2] = fabs(v[2]);
	if(av[0] > av[1] && av[0] > av[2])
	{
		if(v[0] < 0)
			axis = 1;
		else
			axis = 0;
	}
	else if(av[1] > av[2] && av[1] > av[0])
	{
		if(v[1] < 0)
			axis = 3;
		else
			axis = 2;
	}
	else
	{
		if(v[2] < 0)
			axis = 5;
		else
			axis = 4;
	}

	// project new texture coords
	for(i = 0; i < nump; i++, vecs += 3)
	{
		j = vec_to_st[axis][2];
		if(j > 0)
			dv = vecs[j - 1];
		else
			dv = -vecs[-j - 1];
		if(dv < 0.001)
			continue;			// don't divide by zero
		j = vec_to_st[axis][0];
		if(j < 0)
			s = -vecs[-j - 1] / dv;
		else
			s = vecs[j - 1] / dv;
		j = vec_to_st[axis][1];
		if(j < 0)
			t = -vecs[-j - 1] / dv;
		else
			t = vecs[j - 1] / dv;

		if(s < sky_mins[0][axis])
			sky_mins[0][axis] = s;
		if(t < sky_mins[1][axis])
			sky_mins[1][axis] = t;
		if(s > sky_maxs[0][axis])
			sky_maxs[0][axis] = s;
		if(t > sky_maxs[1][axis])
			sky_maxs[1][axis] = t;
	}
}

#define	ON_EPSILON		0.1f	// point on plane side epsilon
#define	MAX_CLIP_VERTS	64
/*
================
ClipSkyPolygon
================
*/
static void ClipSkyPolygon(int nump, vec3_t vecs, int stage)
{
	float          *norm;
	float          *v;
	qboolean        front, back;
	float           d, e;
	float           dists[MAX_CLIP_VERTS];
	int             sides[MAX_CLIP_VERTS];
	vec3_t          newv[2][MAX_CLIP_VERTS];
	int             newc[2];
	int             i, j;

	if(nump > MAX_CLIP_VERTS - 2)
		ri.Error(ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS");
	if(stage == 6)
	{							// fully clipped, so draw it
		AddSkyPolygon(nump, vecs);
		return;
	}

	front = back = qfalse;
	norm = sky_clip[stage];
	for(i = 0, v = vecs; i < nump; i++, v += 3)
	{
		d = DotProduct(v, norm);
		if(d > ON_EPSILON)
		{
			front = qtrue;
			sides[i] = SIDE_FRONT;
		}
		else if(d < -ON_EPSILON)
		{
			back = qtrue;
			sides[i] = SIDE_BACK;
		}
		else
			sides[i] = SIDE_ON;
		dists[i] = d;
	}

	if(!front || !back)
	{							// not clipped
		ClipSkyPolygon(nump, vecs, stage + 1);
		return;
	}

	// clip it
	sides[i] = sides[0];
	dists[i] = dists[0];
	VectorCopy(vecs, (vecs + (i * 3)));
	newc[0] = newc[1] = 0;

	for(i = 0, v = vecs; i < nump; i++, v += 3)
	{
		switch (sides[i])
		{
			case SIDE_FRONT:
				VectorCopy(v, newv[0][newc[0]]);
				newc[0]++;
				break;
			case SIDE_BACK:
				VectorCopy(v, newv[1][newc[1]]);
				newc[1]++;
				break;
			case SIDE_ON:
				VectorCopy(v, newv[0][newc[0]]);
				newc[0]++;
				VectorCopy(v, newv[1][newc[1]]);
				newc[1]++;
				break;
		}

		if(sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
			continue;

		d = dists[i] / (dists[i] - dists[i + 1]);
		for(j = 0; j < 3; j++)
		{
			e = v[j] + d * (v[j + 3] - v[j]);
			newv[0][newc[0]][j] = e;
			newv[1][newc[1]][j] = e;
		}
		newc[0]++;
		newc[1]++;
	}

	// continue
	ClipSkyPolygon(newc[0], newv[0][0], stage + 1);
	ClipSkyPolygon(newc[1], newv[1][0], stage + 1);
}

/*
==============
ClearSkyBox
==============
*/
static void ClearSkyBox(void)
{
	int             i;

	for(i = 0; i < 6; i++)
	{
		sky_mins[0][i] = sky_mins[1][i] = 9999;
		sky_maxs[0][i] = sky_maxs[1][i] = -9999;
	}
}

/*
================
Tess_ClipSkyPolygons
================
*/
void Tess_ClipSkyPolygons()
{
	vec3_t          p[5];		// need one extra point for clipping
	int             i, j;

	ClearSkyBox();

	for(i = 0; i < tess.numIndexes; i += 3)
	{
		for(j = 0; j < 3; j++)
		{
			VectorSubtract(tess.xyz[tess.indexes[i + j]], backEnd.viewParms.orientation.origin, p[j]);
		}

		ClipSkyPolygon(3, p[0], 0);
	}
}

/*
===================================================================================

CLOUD VERTEX GENERATION

===================================================================================
*/

/*
** MakeSkyVec
**
** Parms: s, t range from -1 to 1
*/
static void MakeSkyVec(float s, float t, int axis, vec4_t outSt, vec4_t outXYZ)
{
	// 1 = s, 2 = t, 3 = 2048
	static int      st_to_vec[6][3] = {
		{3, -1, 2},
		{-3, 1, 2},

		{1, 3, 2},
		{-1, -3, 2},

		{-2, -1, 3},			// 0 degrees yaw, look straight up
		{2, -1, -3}				// look straight down
	};

	vec3_t          b;
	int             j, k;
	float           boxSize;

	boxSize = backEnd.viewParms.zFar / 1.75;	// div sqrt(3)
	b[0] = s * boxSize;
	b[1] = t * boxSize;
	b[2] = boxSize;

	for(j = 0; j < 3; j++)
	{
		k = st_to_vec[axis][j];
		if(k < 0)
		{
			outXYZ[j] = -b[-k - 1];
		}
		else
		{
			outXYZ[j] = b[k - 1];
		}
	}
	outXYZ[3] = 1;

	// avoid bilerp seam
	s = (s + 1) * 0.5;
	t = (t + 1) * 0.5;
	if(s < sky_min)
	{
		s = sky_min;
	}
	else if(s > sky_max)
	{
		s = sky_max;
	}

	if(t < sky_min)
	{
		t = sky_min;
	}
	else if(t > sky_max)
	{
		t = sky_max;
	}

	t = 1.0 - t;


	if(outSt)
	{
		outSt[0] = s;
		outSt[1] = t;
		outSt[2] = 0;
		outSt[3] = 1;
	}
}

//static int      sky_texorder[6] = { 0, 2, 1, 3, 4, 5 };
static vec4_t   s_skyPoints[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1];
static float    s_skyTexCoords[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][4];

/*
static void DrawSkySide(struct image_s *image, const int mins[2], const int maxs[2])
{
	int             s, t;

	GL_SelectTexture(0);
	GL_Bind(image);

	for(t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++)
	{
		glBegin(GL_TRIANGLE_STRIP);

		for(s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++)
		{
			glVertexAttrib4fvARB(ATTR_INDEX_TEXCOORD0, s_skyTexCoords[t][s]);
			glVertexAttrib4fvARB(ATTR_INDEX_POSITION, s_skyPoints[t][s]);

			glVertexAttrib4fvARB(ATTR_INDEX_TEXCOORD0, s_skyTexCoords[t + 1][s]);
			glVertexAttrib4fvARB(ATTR_INDEX_POSITION, s_skyPoints[t + 1][s]);
		}

		glEnd();
	}
}
*/

static void FillCloudySkySide(const int mins[2], const int maxs[2], qboolean addIndexes)
{
	int             s, t;
	int             vertexStart = tess.numVertexes;
	int             tHeight, sWidth;

	tHeight = maxs[1] - mins[1] + 1;
	sWidth = maxs[0] - mins[0] + 1;

	for(t = mins[1] + HALF_SKY_SUBDIVISIONS; t <= maxs[1] + HALF_SKY_SUBDIVISIONS; t++)
	{
		for(s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++)
		{
			VectorAdd(s_skyPoints[t][s], backEnd.viewParms.orientation.origin, tess.xyz[tess.numVertexes]);
			tess.xyz[tess.numVertexes][3] = 1;

			tess.texCoords[tess.numVertexes][0] = s_skyTexCoords[t][s][0];
			tess.texCoords[tess.numVertexes][1] = s_skyTexCoords[t][s][1];
			tess.texCoords[tess.numVertexes][2] = 0;
			tess.texCoords[tess.numVertexes][3] = 1;

			tess.numVertexes++;

			if(tess.numVertexes >= SHADER_MAX_VERTEXES)
			{
				ri.Error(ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n");
			}
		}
	}

	// only add indexes for one pass, otherwise it would draw multiple times for each pass
	if(addIndexes)
	{
		for(t = 0; t < tHeight - 1; t++)
		{
			for(s = 0; s < sWidth - 1; s++)
			{
				tess.indexes[tess.numIndexes] = vertexStart + s + t * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth);
				tess.numIndexes++;

				tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth);
				tess.numIndexes++;
			}
		}
	}
}

static void DrawSkyBox(shader_t * shader)
{
	int             i;

	sky_min = 0;
	sky_max = 1;

	Com_Memset(s_skyTexCoords, 0, sizeof(s_skyTexCoords));

	// set up for drawing
	tess.multiDrawPrimitives = 0;
	tess.numIndexes = 0;
	tess.numVertexes = 0;

	for(i = 0; i < 6; i++)
	{
		int             sky_mins_subd[2], sky_maxs_subd[2];
		int             s, t;

		sky_mins[0][i] = floor(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_mins[1][i] = floor(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[0][i] = ceil(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[1][i] = ceil(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;

		if((sky_mins[0][i] >= sky_maxs[0][i]) || (sky_mins[1][i] >= sky_maxs[1][i]))
		{
			continue;
		}

		sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS;

		if(sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
		if(sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;

		if(sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
		if(sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;

		// iterate through the subdivisions
		for(t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++)
		{
			for(s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++)
			{
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
						   (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
						   i, s_skyTexCoords[t][s], s_skyPoints[t][s]);
			}
		}

		//DrawSkySide(shader->sky.outerbox[sky_texorder[i]], sky_mins_subd, sky_maxs_subd);

		// only add indexes for first stage
		FillCloudySkySide(sky_mins_subd, sky_maxs_subd, qtrue);
	}

	// Tr3B: FIXME analyze required vertex attribs by the current material
	Tess_UpdateVBOs(0);

	Tess_DrawElements();
}

static void FillCloudBox(const shader_t * shader, int stage)
{
	int             i;

	for(i = 0; i < 6; i++)
	{
		int             sky_mins_subd[2], sky_maxs_subd[2];
		int             s, t;
		float           MIN_T;

		if(1)					// FIXME? shader->sky.fullClouds )
		{
			MIN_T = -HALF_SKY_SUBDIVISIONS;

			// still don't want to draw the bottom, even if fullClouds
			if(i == 5)
				continue;
		}
		else
		{
			switch (i)
			{
				case 0:
				case 1:
				case 2:
				case 3:
					MIN_T = -1;
					break;
				case 5:
					// don't draw clouds beneath you
					continue;
				case 4:		// top
				default:
					MIN_T = -HALF_SKY_SUBDIVISIONS;
					break;
			}
		}

		sky_mins[0][i] = floor(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_mins[1][i] = floor(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[0][i] = ceil(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[1][i] = ceil(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;

		if((sky_mins[0][i] >= sky_maxs[0][i]) || (sky_mins[1][i] >= sky_maxs[1][i]))
		{
			continue;
		}

		sky_mins_subd[0] = Q_ftol(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS);
		sky_mins_subd[1] = Q_ftol(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS);
		sky_maxs_subd[0] = Q_ftol(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS);
		sky_maxs_subd[1] = Q_ftol(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS);

		if(sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
		if(sky_mins_subd[1] < MIN_T)
			sky_mins_subd[1] = MIN_T;
		else if(sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS)
			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;

		if(sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
		else if(sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
		if(sky_maxs_subd[1] < MIN_T)
			sky_maxs_subd[1] = MIN_T;
		else if(sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS)
			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;

		// iterate through the subdivisions
		for(t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++)
		{
			for(s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++)
			{
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
						   (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS, i, NULL, s_skyPoints[t][s]);

				s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0];
				s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1];
			}
		}

		// only add indexes for first stage
		FillCloudySkySide(sky_mins_subd, sky_maxs_subd, (qboolean) (stage == 0));
	}
}

static void BuildCloudData()
{
	int             i;
	shader_t       *shader;

	shader = tess.surfaceShader;

	assert(shader->isSky);

	sky_min = 1.0 / 256.0f;		// FIXME: not correct?
	sky_max = 255.0 / 256.0f;

	// set up for drawing
	tess.multiDrawPrimitives = 0;
	tess.numIndexes = 0;
	tess.numVertexes = 0;

	if(tess.surfaceShader->sky.cloudHeight)
	{
		for(i = 0; i < MAX_SHADER_STAGES; i++)
		{
			if(!tess.surfaceStages[i])
			{
				break;
			}

			FillCloudBox(tess.surfaceShader, i);
		}
	}

	// Tr3B: FIXME analyze required vertex attribs by the current material
	Tess_UpdateVBOs(0);
}

/*
** R_InitSkyTexCoords
** Called when a sky shader is parsed
*/
void R_InitSkyTexCoords(float heightCloud)
{
	int             i, s, t;
	float           radiusWorld = 4096;
	float           p;
	float           sRad, tRad;
	vec4_t          skyVec;
	vec3_t          v;

	// init zfar so MakeSkyVec works even though
	// a world hasn't been bounded
	backEnd.viewParms.zFar = 1024;

	for(i = 0; i < 6; i++)
	{
		for(t = 0; t <= SKY_SUBDIVISIONS; t++)
		{
			for(s = 0; s <= SKY_SUBDIVISIONS; s++)
			{
				// compute vector from view origin to sky side integral point
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
						   (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS, i, NULL, skyVec);

				// compute parametric value 'p' that intersects with cloud layer
				p = (1.0f / (2 * DotProduct(skyVec, skyVec))) *
					(-2 * skyVec[2] * radiusWorld +
					 2 * sqrt(SQR(skyVec[2]) * SQR(radiusWorld) +
							  2 * SQR(skyVec[0]) * radiusWorld * heightCloud +
							  SQR(skyVec[0]) * SQR(heightCloud) +
							  2 * SQR(skyVec[1]) * radiusWorld * heightCloud +
							  SQR(skyVec[1]) * SQR(heightCloud) +
							  2 * SQR(skyVec[2]) * radiusWorld * heightCloud + SQR(skyVec[2]) * SQR(heightCloud)));

				s_cloudTexP[i][t][s] = p;

				// compute intersection point based on p
				VectorScale(skyVec, p, v);
				v[2] += radiusWorld;

				// compute vector from world origin to intersection point 'v'
				VectorNormalize(v);

				sRad = Q_acos(v[0]);
				tRad = Q_acos(v[1]);

				s_cloudTexCoords[i][t][s][0] = sRad;
				s_cloudTexCoords[i][t][s][1] = tRad;
			}
		}
	}
}

//======================================================================================

/*
** RB_DrawSun
*/
void RB_DrawSun(void)
{
#if 0
	float           size;
	float           dist;
	vec3_t          origin, vec1, vec2;
	vec3_t          temp;
	matrix_t        transformMatrix;
	matrix_t        modelViewMatrix;

	if(!backEnd.skyRenderedThisView)
	{
		return;
	}
	if(!r_drawSun->integer)
	{
		return;
	}

	GL_PushMatrix();

	GL_BindProgram(&tr.genericShader);

	// set uniforms
	GLSL_SetUniform_TCGen_Environment(&tr.genericShader,  qfalse);
	GLSL_SetUniform_InverseVertexColor(&tr.genericShader,  qfalse);
	if(glConfig2.vboVertexSkinningAvailable)
	{
		GLSL_SetUniform_VertexSkinning(&tr.genericShader, qfalse);
	}
	GLSL_SetUniform_DeformGen(&tr.genericShader, DGEN_NONE);
	GLSL_SetUniform_AlphaTest(&tr.genericShader, -1.0);

	MatrixSetupTranslation(transformMatrix, backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1],
						   backEnd.viewParms.orientation.origin[2]);
	MatrixMultiply(backEnd.viewParms.world.viewMatrix, transformMatrix, modelViewMatrix);

	GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix);
	GL_LoadModelViewMatrix(modelViewMatrix);

	GLSL_SetUniform_ModelMatrix(&tr.genericShader, backEnd.orientation.transformMatrix);
	GLSL_SetUniform_ModelViewProjectionMatrix(&tr.genericShader, glState.modelViewProjectionMatrix[glState.stackIndex]);

	GLSL_SetUniform_PortalClipping(&tr.genericShader, backEnd.viewParms.isPortal);
	if(backEnd.viewParms.isPortal)
	{
		float           plane[4];

		// clipping plane in world space
		plane[0] = backEnd.viewParms.portalPlane.normal[0];
		plane[1] = backEnd.viewParms.portalPlane.normal[1];
		plane[2] = backEnd.viewParms.portalPlane.normal[2];
		plane[3] = backEnd.viewParms.portalPlane.dist;

		GLSL_SetUniform_PortalPlane(&tr.genericShader, plane);
	}


	dist = backEnd.viewParms.skyFar / 1.75;	// div sqrt(3)
	size = dist * 0.4;

	VectorScale(tr.sunDirection, dist, origin);
	PerpendicularVector(vec1, tr.sunDirection);
	CrossProduct(tr.sunDirection, vec1, vec2);

	VectorScale(vec1, size, vec1);
	VectorScale(vec2, size, vec2);

	// farthest depth range
	glDepthRange(1.0, 1.0);

	// FIXME: use quad stamp
	Tess_Begin(Tess_StageIteratorGeneric, tr.sunShader, NULL, tess.skipTangentSpaces, qfalse, -1, tess.fogNum);
	VectorCopy(origin, temp);
	VectorSubtract(temp, vec1, temp);
	VectorSubtract(temp, vec2, temp);
	VectorCopy(temp, tess.xyz[tess.numVertexes]);
	tess.xyz[tess.numVertexes][3] = 1;
	tess.texCoords[tess.numVertexes][0] = 0;
	tess.texCoords[tess.numVertexes][1] = 0;
	tess.texCoords[tess.numVertexes][2] = 0;
	tess.texCoords[tess.numVertexes][3] = 1;
	tess.colors[tess.numVertexes][0] = 1;
	tess.colors[tess.numVertexes][1] = 1;
	tess.colors[tess.numVertexes][2] = 1;
	tess.numVertexes++;

	VectorCopy(origin, temp);
	VectorAdd(temp, vec1, temp);
	VectorSubtract(temp, vec2, temp);
	VectorCopy(temp, tess.xyz[tess.numVertexes]);
	tess.xyz[tess.numVertexes][3] = 1;
	tess.texCoords[tess.numVertexes][0] = 0;
	tess.texCoords[tess.numVertexes][1] = 1;
	tess.texCoords[tess.numVertexes][2] = 0;
	tess.texCoords[tess.numVertexes][3] = 1;
	tess.colors[tess.numVertexes][0] = 1;
	tess.colors[tess.numVertexes][1] = 1;
	tess.colors[tess.numVertexes][2] = 1;
	tess.numVertexes++;

	VectorCopy(origin, temp);
	VectorAdd(temp, vec1, temp);
	VectorAdd(temp, vec2, temp);
	VectorCopy(temp, tess.xyz[tess.numVertexes]);
	tess.xyz[tess.numVertexes][3] = 1;
	tess.texCoords[tess.numVertexes][0] = 1;
	tess.texCoords[tess.numVertexes][1] = 1;
	tess.texCoords[tess.numVertexes][2] = 0;
	tess.texCoords[tess.numVertexes][3] = 1;
	tess.colors[tess.numVertexes][0] = 1;
	tess.colors[tess.numVertexes][1] = 1;
	tess.colors[tess.numVertexes][2] = 1;
	tess.numVertexes++;

	VectorCopy(origin, temp);
	VectorSubtract(temp, vec1, temp);
	VectorAdd(temp, vec2, temp);
	VectorCopy(temp, tess.xyz[tess.numVertexes]);
	tess.xyz[tess.numVertexes][3] = 1;
	tess.texCoords[tess.numVertexes][0] = 1;
	tess.texCoords[tess.numVertexes][1] = 0;
	tess.texCoords[tess.numVertexes][2] = 0;
	tess.texCoords[tess.numVertexes][3] = 1;
	tess.colors[tess.numVertexes][0] = 1;
	tess.colors[tess.numVertexes][1] = 1;
	tess.colors[tess.numVertexes][2] = 1;
	tess.numVertexes++;

	tess.indexes[tess.numIndexes++] = 0;
	tess.indexes[tess.numIndexes++] = 1;
	tess.indexes[tess.numIndexes++] = 2;
	tess.indexes[tess.numIndexes++] = 0;
	tess.indexes[tess.numIndexes++] = 2;
	tess.indexes[tess.numIndexes++] = 3;

	Tess_End();

	// back to normal depth range
	glDepthRange(0.0, 1.0);

	GL_PopMatrix();
#endif
}




/*
================
Tess_StageIteratorSky

All of the visible sky triangles are in tess

Other things could be stuck in here, like birds in the sky, etc
================
*/
void Tess_StageIteratorSky(void)
{
#if !defined(USE_D3D10)
	// log this call
	if(r_logFile->integer)
	{
		// don't just call LogComment, or we will get
		// a call to va() every frame!
		GLimp_LogComment(va
						 ("--- Tess_StageIteratorSky( %s, %i vertices, %i triangles ) ---\n", tess.surfaceShader->name,
						  tess.numVertexes, tess.numIndexes / 3));
	}

	if(r_fastsky->integer)
	{
		return;
	}

	// trebor: HACK why does this happen with cg_draw2D 0 ?
	if(tess.stageIteratorFunc2 == NULL)
	{
		//tess.stageIteratorFunc2 = Tess_StageIteratorGeneric;
		ri.Error(ERR_FATAL, "tess.stageIteratorFunc == NULL");
	}

	if(tess.stageIteratorFunc2 == &Tess_StageIteratorDepthFill)
	{
		// go through all the polygons and project them onto
		// the sky box to see which blocks on each side need
		// to be drawn
		Tess_ClipSkyPolygons();

		// generate the vertexes for all the clouds, which will be drawn
		// by the generic shader routine
		BuildCloudData();

		if(tess.numVertexes || tess.multiDrawPrimitives)
			tess.stageIteratorFunc2();
	}
	else
	{

		if(tess.stageIteratorFunc2 == &Tess_StageIteratorGBuffer)
		{
			R_BindFBO(tr.geometricRenderFBO);
		}

		// go through all the polygons and project them onto
		// the sky box to see which blocks on each side need
		// to be drawn
		Tess_ClipSkyPolygons();

		// r_showSky will let all the sky blocks be drawn in
		// front of everything to allow developers to see how
		// much sky is getting sucked in
		
		if(r_showSky->integer)
		{
			glDepthRange(0.0, 0.0);
		}
		else
		{
			glDepthRange(1.0, 1.0);
		}

		// draw the outer skybox
		if(tess.surfaceShader->sky.outerbox && tess.surfaceShader->sky.outerbox != tr.blackCubeImage)
		{
#if 1
			R_BindVBO(tess.vbo);
			R_BindIBO(tess.ibo);

			gl_skyboxShader->SetPortalClipping(backEnd.viewParms.isPortal);
			gl_skyboxShader->BindProgram();
			
			gl_skyboxShader->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin);	// in world space

			gl_skyboxShader->SetUniform_ModelMatrix(backEnd.orientation.transformMatrix);
			gl_skyboxShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]);

			// u_PortalPlane
			if(backEnd.viewParms.isPortal)
			{
				float           plane[4];

				// clipping plane in world space
				plane[0] = backEnd.viewParms.portalPlane.normal[0];
				plane[1] = backEnd.viewParms.portalPlane.normal[1];
				plane[2] = backEnd.viewParms.portalPlane.normal[2];
				plane[3] = backEnd.viewParms.portalPlane.dist;

				gl_skyboxShader->SetUniform_PortalPlane(plane);
			}

			gl_skyboxShader->SetRequiredVertexPointers();

			// bind u_ColorMap
			GL_SelectTexture(0);
			GL_Bind(tess.surfaceShader->sky.outerbox);

			DrawSkyBox(tess.surfaceShader);
#endif
		}
		

		// generate the vertexes for all the clouds, which will be drawn
		// by the generic shader routine
		BuildCloudData();

		if(tess.numVertexes || tess.multiDrawPrimitives)
			tess.stageIteratorFunc2();

		// Tr3B: TODO draw the inner skybox?

		if(tess.stageIteratorFunc2 == Tess_StageIteratorGBuffer)
		{
			R_BindNullFBO();
		}

		if(tess.stageIteratorFunc2 != Tess_StageIteratorDepthFill)
		{
			// back to normal depth range
			glDepthRange(0.0, 1.0);

			// note that sky was drawn so we will draw a sun later
			backEnd.skyRenderedThisView = qtrue;
		}
	}
#endif
}

